#include "ET++.ph"
#include "Converter.h"

#include "Class.h"
#include "Port.h"
#include "Bitmap.h"
#include "DevBitmap.h"
#include "Picture.h"
#include "PictPort.h"
#include "String.h"
#include "Data.h"
#include "WindowColorMap.h"
#include "TypeMatcher.h"
#include "ET_stdio.h"

static const Symbol cDocTypeMacPict("MACPICT");

#include "pict.xpm3"
SmartBitmap gPictIcon(pict);

Ink *mode[8];

static struct {
	Ink *ink;
	char bits[8];
} patmap[30];

static struct {
	int id;
	GrFont fid;
} fmap[10] = {
	{ 0,  eFontChicago },
	{ 1,  eFontHelvetica },
	{ 2,  eFontTimes },
	{ 3,  eFontHelvetica },
	{ 4,  eFontCourier },
	{ 20, eFontTimes },
	{ 21, eFontHelvetica },
	{ 22, eFontCourier },
	{ 23, eFontSymbol },
	{ 15011, eFontAvantgarde }
};

static void getrect(IStream &is, Rectangle &r)
{
	r.origin.y= (short) is.GetBigEndian(2);
	r.origin.x= (short) is.GetBigEndian(2);
	r.extent.y= (short) is.GetBigEndian(2) - r.origin.y;
	r.extent.x= (short) is.GetBigEndian(2) - r.origin.x;
}

static Ink *FindInk(IStream &is)
{
	byte pat[8];
	int i;

	if (patmap[0].ink == 0) {
		patmap[0].ink= gInkWhite;
		patmap[1].ink= gInkBlack;
		memset(&patmap[1].bits, 0xff, 8);
	}

	is.read(pat, 8);

	for (i= 0; patmap[i].ink; i++)
		if (strncmp(patmap[i].bits, (char*)pat, 8) == 0)
			return patmap[i].ink;

	strncpy(patmap[i].bits, (char*)pat, 8);
	Bitmap *bm= new Bitmap(gPoint8);
	patmap[i].ink= bm;
	DevBitmap *dbm= bm->GetDevBitmap();
	BitmapInfo bi(bm->Size(), 1, 1);
	for (int y= 0; y < 8; y++)
		dbm->SetRow(&bi, y, &pat[y]);
	return bm;
}

static void readpixmap(IStream &is, Port *port, bool draw, bool direct, bool region)
{
	register int x, y, l, n;
	register DevBitmap *dbm;
	Bitmap *bm;
	Rectangle r, src, dst;
	int depth, count, packtype, rowbytes;

	if (direct)
		is.GetBigEndian(4); /* baseAddr */

	rowbytes= (short) is.GetBigEndian(2);    /* rowbytes */
	getrect(is, r);

	if (rowbytes & 0x8000) {
		rowbytes= rowbytes & 0x7fff;
		is.GetBigEndian(2); /* version */
		packtype= (int) is.GetBigEndian(2); /* packType */
		is.GetBigEndian(4); /* packSize */
		is.GetBigEndian(4); /* hres.hres */
		is.GetBigEndian(4); /* vres.vres */
		is.GetBigEndian(2); /* pixelType */
		is.GetBigEndian(2); /* pixelSize */

		count= (int) is.GetBigEndian(2);  /* cmpCount */
		depth= (int) is.GetBigEndian(2);  /* cmpSize */
		depth= depth * count;
		
		is.GetBigEndian(4); /* planeBytes */
		is.GetBigEndian(4); /* pmTable */
		is.GetBigEndian(4); /* pmReserved */
		bm= new Bitmap(r.extent, depth);
		if (!direct) {
			is.GetBigEndian(4); /* ctSeed */
			is.GetBigEndian(2);
			int cl= (int) is.GetBigEndian(2);
				
			if (cl >= 0) {
				u_short r, g, b, v;
				for (int i= 0; i <= cl; i++) {
					v= (u_short) is.GetBigEndian(2);
					r= (u_short) is.GetBigEndian(2);
					g= (u_short) is.GetBigEndian(2);
					b= (u_short) is.GetBigEndian(2);
					RGB rgb(r/256, g/256, b/256);
					bm->ChangeRGB(i, &rgb);
				}
			}
		}
	} else {
		depth= 1;
		bm= new Bitmap(r.extent, depth);
	}

	getrect(is, src);
	getrect(is, dst);
	is.GetBigEndian(2); /* mode */

	if (region)
		is.seek((short) is.GetBigEndian(2)-2, TRUE); // skip region
		
	byte *buf= new byte[rowbytes];
	dbm= bm->GetDevBitmap();
	BitmapInfo bi(bm->Size(), depth, 1);

	for (y= 0; y < r.extent.y; y++) {
		register byte *bp= buf;
		
		if (rowbytes < 8) {
			is.read(bp, rowbytes);
			bp+= rowbytes;
		} else {
			if (rowbytes > 250)
				n= (short) is.GetBigEndian(2);
			else
				n= is.GetByte();
				
			while (n > 0) {
				l= is.GetChar();
				n--;
				if (l < 0) {
					l= -l+1;
					memset(bp, is.GetByte(), l);
					n--;
				} else {
					l++;
					is.read(bp, l);
					n-= l;
				}
				bp+= l;
			}
		}
		if (packtype == 4) {
			int l= (bp-buf)/3;
			for (x= 0; x < r.extent.x; x++) {
				RGB rgb(buf[x], buf[l+x], buf[l+l+x]);
				bm->SetRGB(x, y, &rgb);
			}
		} else
			dbm->SetRow(&bi, y, buf);
	}

	delete buf;

	if (bm && draw)
		port->ShowBitmap(gInkBlack, dst, bm);
}

static Ink *readcolor(IStream &is)
{
	u_int r, g, b;
	r= (u_int) is.GetBigEndian(2);
	g= (u_int) is.GetBigEndian(2);
	b= (u_int) is.GetBigEndian(2);
	return new_RGBColor(r / 257, g / 257, b / 257);
}

static void MacPictParse(IStream &is, Port *port, int version)
{
	register int i, l, c;
	u_int op;
	int npts= 0, done= 0, code, num= 1, denom= 1, pensize= 1, ps= 12;
	byte s[10000];
	short a1, a2, a3, a4;
	GrFont font= eFontTimes;
	GrFace face= eFacePlain;
	Rectangle cliprect, r;
	Point origin, rad(4), tp, p1, p, d, pts[1000];
	bool draw= TRUE, stroke= TRUE;
	Rectangle oldclip(port->GetCliprect());

	mode[0]= gInkBlack;
	mode[1]= gInkBlack;
	mode[2]= gInkWhite;
	mode[3]= gInkXor;
	mode[4]= gInkBlack;
	mode[5]= gInkBlack;
	mode[6]= gInkBlack;
	mode[7]= gInkBlack;

	while (! done && is) {

		if (version == 1) {
			op= is.GetByte();
		} else {
			if (is.tell() & 1)
				is.GetByte();   // pad
			op= (short) is.GetBigEndian(2);
		}
		
		switch (op) {
		case 0x00:      /* no op */
				break;
		case 1: /* clip */
				c= (short) is.GetBigEndian(2);
				getrect(is, cliprect);
				if (cliprect.IsNotEmpty()) {
					draw= TRUE;
					port->Clip(oldclip);
					port->ClipFurther(cliprect);
				} else
					draw= FALSE;
				is.seek(c-10, TRUE);
				break;
		case 2: /* background pattern */
				mode[2]= FindInk(is);
				break;
		case 3: /* text font */
				c= (short) is.GetBigEndian(2);
				font= eFontDefault;
				for (i= 0; i < 10; i++)
					if (fmap[i].id == c) {
						font= fmap[i].fid;
						break;
					}
				if (i >= 10)
					fprintf(stderr, "Font ?: %d\n", c);
				break;
		case 4: /* text face */
				face= (GrFace) is.GetByte();
				// face= eFacePlain;
				break;
		case 5: /* text mode */
				is.GetBigEndian(2);
				break;
		case 6: /* space extra */
				is.seek(4, TRUE);
				break;
		case 7: /* pen size */
				pensize= (short) is.GetBigEndian(2);
				a1= (short) is.GetBigEndian(2);
				if (pensize == 0 && a1 == 0)
					stroke= FALSE;
				else {
					stroke= TRUE;
					if (pensize != a1)
						pensize= (pensize+a1)/2;
					pensize= (pensize*num)/denom;
				}
				break;
		case 8: /* pen mode */
				is.GetBigEndian(2);
				break;
		case 9: /* pen pat */
				mode[1]= FindInk(is);
				break;
		case 0x0a:      /* fill pat */
				mode[4]= FindInk(is);
				break;
		case 0x0b:      /* oval size */
				rad.y= (short) is.GetBigEndian(2);
				rad.x= (short) is.GetBigEndian(2);
				break;
		case 0x0c:      /* origin */
				origin.y= (short) is.GetBigEndian(2);
				origin.x= (short) is.GetBigEndian(2);
				fprintf(stderr, "origin: %d,%d\n", origin.x, origin.y);
				break;
		case 0x0d:      /* text size */
				ps= (short) is.GetBigEndian(2);
				break;
		case 0x0e:      /* fg color */
				is.seek(4, TRUE);
				break;
		case 0x0f:      /* bg color */
				is.seek(4, TRUE);
				break;
		case 0x10:      /* text ratio */
				is.seek(8, TRUE);
				break;
		case 0x11:      /* version */
				version= is.GetByte();
				break;

		case 0x12:
		case 0x13:
		case 0x14:
				break;

		case 0x15:
				is.GetBigEndian(2);
				break;
		case 0x16:
				is.GetBigEndian(2);
				break;
				
		case 0x17:
		case 0x18:
		case 0x19:
				break;
				
		case 0x1a:
				mode[1]= readcolor(is);
				break;
		case 0x1b:
				mode[2]= readcolor(is);
				break;
				
		case 0x1c:  // highlight flag
				break;
		case 0x1d:
				readcolor(is);
				break;
		case 0x1e:  // default highlight color
				break;
		case 0x1f:
				readcolor(is);
				break;

		case 0x20:      /* line from, to */
				p1.y= (short) is.GetBigEndian(2);
				p1.x= (short) is.GetBigEndian(2);
				p.y= (short) is.GetBigEndian(2);
				p.x= (short) is.GetBigEndian(2);
				if (stroke && draw)
					port->StrokeLine(mode[1], pensize, eDefaultCap, p1, p);
				break;
		case 0x21:      /* line from */
				p1= p;
				p.y= (short) is.GetBigEndian(2);
				p.x= (short) is.GetBigEndian(2);
				if (stroke && draw)
					port->StrokeLine(mode[1], pensize, eDefaultCap, p1, p);
				break;
		case 0x22:      /* short line from, to(dh, dv) */
				p.y= (short) is.GetBigEndian(2);
				p.x= (short) is.GetBigEndian(2);
				d.x= is.GetChar();
				d.y= is.GetChar();
				if (stroke && draw)
					port->StrokeLine(mode[1], pensize, eDefaultCap, p, p+d);
				p+= d;
				break;
		case 0x23:      /* short line from (dh, dv) */
				d.x= is.GetChar();
				d.y= is.GetChar();
				if (stroke && draw)
					port->StrokeLine(mode[1], pensize, eDefaultCap, p, p+d);
				p+= d;
				break;

		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
				is.seek((short) is.GetBigEndian(2), TRUE);
				break;

		case 0x28:      /* long text */
				tp.y= (short) is.GetBigEndian(2);
				tp.x= (short) is.GetBigEndian(2);
				l= is.GetByte();
				is.read(s, l);
				if (draw)
					port->ShowString(new_Font(font, ps, face), mode[1], tp, (byte*)s, l);
				break;
		case 0x29:      /* dh text */
				tp.x+= is.GetByte();
				l= is.GetByte();
				is.read(s, l);
				if (draw)
					port->ShowString(new_Font(font, ps, face), mode[1], tp, (byte*)s, l);
				break;
		case 0x2a:      /* dv text */
				tp.y+= is.GetByte();
				l= is.GetByte();
				is.read(s, l);
				if (draw)
					port->ShowString(new_Font(font, ps, face), mode[1], tp, (byte*)s, l);
				break;
		case 0x2b:      /* dh, dv text */
				tp.x+= is.GetByte();
				tp.y+= is.GetByte();
				l= is.GetByte();
				is.read(s, l);
				if (draw)
					port->ShowString(new_Font(font, ps, face), mode[1], tp, (byte*)s, l);
				break;

		case 0x2c:
		case 0x2d:
		case 0x2e:
		case 0x2f:
				is.seek((short) is.GetBigEndian(2), TRUE);
				break;

		case 0x30:      /* framerect */
				getrect(is, r);
				if (stroke && draw) port->StrokeRect(mode[1], pensize, r);
				break;
		case 0x31:      /* paintrect */
		case 0x32:      /* eraserect */
		case 0x33:      /* invertrect */
		case 0x34:      /* fillrect */
		case 0x35:
		case 0x36:
		case 0x37:
				getrect(is, r);
				if (draw)
					port->FillRect(mode[op & 7], r);
				break;
				
		case 0x38:      /* frame same rect */
				if (stroke && draw) port->StrokeRect(mode[1], pensize, r);
				break;
		case 0x39:      /* paint same rect */
		case 0x3a:      /* erase same rect */
		case 0x3b:      /* invert same rect */
		case 0x3c:      /* fill same rect */
		case 0x3d:
		case 0x3e:
		case 0x3f:
				if (draw)
					port->FillRect(mode[op & 7], r);
				break;
				
		case 0x40:      /* frame R rect */
				getrect(is, r);
				if (stroke && draw)
					port->StrokeRRect(mode[1], pensize, r, rad);
				break;
		case 0x41:      /* paint R rect */
		case 0x42:      /* erase R rect */
		case 0x43:      /* invert R rect */
		case 0x44:      /* fill R rect */
		case 0x45:
		case 0x46:
		case 0x47:
				getrect(is, r);
				if (draw)
					port->FillRRect(mode[op & 7], r, rad);
				break;

		case 0x48:      /* frame same R rect */
				if (stroke && draw)
					port->StrokeRRect(mode[1], pensize, r, rad);
				break;
		case 0x49:      /* paint same R rect */
		case 0x4a:      /* erase same R rect */
		case 0x4b:      /* invert same R rect */
		case 0x4c:      /* fill same R rect */
		case 0x4d:
		case 0x4e:
		case 0x4f:
				if (draw)
					port->FillRRect(mode[op & 7], r, rad);
				break;
				
		case 0x50:      /* frame oval */
				getrect(is, r);
				if (stroke && draw)
					port->StrokeOval(mode[1], pensize, r);
				break;
		case 0x51:      /* paint oval */
		case 0x52:      /* erase oval */
		case 0x53:      /* invert oval */
		case 0x54:      /* fill oval */
		case 0x55:
		case 0x56:
		case 0x57:
				getrect(is, r);
				if (draw)
					port->FillOval(mode[op & 7], r);
				break;

		case 0x58:      /* frame same oval */
				if (stroke && draw)
					port->StrokeOval(mode[1], pensize, r);
				break;
		case 0x59:      /* paint same oval */
		case 0x5a:      /* erase same oval */
		case 0x5b:      /* invert same oval */
		case 0x5c:      /* fill same oval */
		case 0x5d:
		case 0x5e:
		case 0x5f:
				if (draw)
					port->FillOval(mode[op & 7], r);
				break;
				

		case 0x60:      /* frame arc */
				getrect(is, r);
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				if (stroke && draw)
					port->StrokeWedge(mode[1], pensize, eDefaultCap, r, a1, a2);
				break;
		case 0x61:      /* paint arc */
		case 0x62:      /* erase arc */
		case 0x63:      /* invert arc */
		case 0x64:      /* fill arc */
		case 0x65:
		case 0x66:
		case 0x67:
				getrect(is, r);
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				if (draw)
					port->FillWedge(mode[op & 7], r, a1, a2);
				break;

		case 0x68:      /* frame same arc */
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				if (stroke && draw)
					port->StrokeWedge(mode[1], pensize, eDefaultCap, r, a1, a2);
				break;
		case 0x69:      /* paint same arc */
		case 0x6a:      /* erase same arc */
		case 0x6b:      /* invert same arc */
		case 0x6c:      /* fill same arc */
		case 0x6d:
		case 0x6e:
		case 0x6f:
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				if (draw)
					port->FillWedge(mode[op & 7], r, a1, a2);
				break;

		case 0x70:      /* frame poly */
				c= (short) is.GetBigEndian(2);
				getrect(is, r);
				npts= (c-10)/4;
				for (i= 0; i < npts; i++) {
					pts[i].y= (short) is.GetBigEndian(2);
					pts[i].x= (short) is.GetBigEndian(2);
				}
				if (stroke && draw)
					port->StrokePolygon(gPoint0, mode[1], pts, npts,
									ePolyDefault, pensize, eDefaultCap);
				break;
		case 0x71:      /* paint poly */
		case 0x72:      /* erase poly */
		case 0x73:      /* invert poly */
		case 0x74:      /* fill poly */
		case 0x75:
		case 0x76:
		case 0x77:
				c= (short) is.GetBigEndian(2);
				getrect(is, r);
				npts= (c-10)/4;
				for (i= 0; i < npts; i++) {
					pts[i].y= (short) is.GetBigEndian(2);
					pts[i].x= (short) is.GetBigEndian(2);
				}
				if (draw)
					port->FillPolygon(gPoint0, mode[op & 7], pts, npts, ePolyDefault);
				break;

		case 0x78:      /* frame same poly */
				if (stroke && draw)
					port->StrokePolygon(gPoint0, mode[1], pts, npts, ePolyDefault, pensize, eDefaultCap);
				break;
		case 0x79:      /* paint same poly */
		case 0x7a:      /* erase same poly */
		case 0x7b:      /* invert same poly */
		case 0x7c:      /* fill same poly */
		case 0x7d:
		case 0x7e:
		case 0x7f:
				if (draw)
					port->FillPolygon(gPoint0, mode[op & 7], pts, npts, ePolyDefault);
				break;

		case 0x80:      /* frame region */
		case 0x81:      /* paint region */
		case 0x82:      /* erase region */
		case 0x83:      /* invert region */
		case 0x84:      /* fill region */
		case 0x85:
		case 0x86:
		case 0x87:
				c= (short) is.GetBigEndian(2);
				getrect(is, r);
				is.seek(c-10, TRUE);
				break;
				
		case 0x88:
		case 0x89:
		case 0x8a:
		case 0x8b:
		case 0x8c:
		case 0x8d:
		case 0x8e:
		case 0x8f:
				break;
				
		case 0x90:
		case 0x98:
			readpixmap(is, port, draw, FALSE, FALSE);
			break;
		
		case 0x91:
		case 0x99:
			readpixmap(is, port, draw, FALSE, TRUE);
			break;

		case 0x9a:
			readpixmap(is, port, draw, TRUE, FALSE);
			break;

		case 0x9b:
			readpixmap(is, port, draw, TRUE, TRUE);
			break;

		case 0xa0:      /* comment */
			code= (short) is.GetBigEndian(2);
			l= 0;
			goto Comment;

		case 0xa1:      /* comment */
			code= (short) is.GetBigEndian(2);
			l= (short) is.GetBigEndian(2);
Comment:
			switch (code) {
			case 150:   // TextBegin
				break;
			case 151:   // TextEnd
				break;
			case 152:   // StringBegin
				break;
			case 153:   // StringEnd
				break;
			case 154:   // TextCenter
				break;
			case 155:   // LineLayoutOff
				break;
			case 156:   // LineLayoutOn
				break;
			case 160:   // PolyBegin
				break;
			case 161:   // PolyEnd
				break;
			case 163:   // PolyIgnore
				break;
			case 164:   // PolySmooth
				break;
			case 165:   // PolyClose
				break;

			case 180:   // DashedLine
				is.read(s, l);
				port->GiveHint(66666, l, s);
				l= 0;
				break;
			case 181:   // DashedStop
				port->GiveHint(66667, 0, 0);
				break;

			case 182:   /* FractLineWidth */
				num= (short) is.GetBigEndian(2);
				denom= (short) is.GetBigEndian(2);
				l-= 4;
				break;

			case 196:
			case 190:
				port->GiveHint(eHintEPSFStart, 0, 0);
				break;
			case 191:
				port->GiveHint(eHintEPSFEnd, 0, 0);
				break;
			case 192:
				break;

			case 200:
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				port->GiveHint(77777, -1, form("%d rotate\n", 360-a2));
				l-= 4;
				break;
			case 201:
				port->GiveHint(77778, -1, form("grestore\n"));
				break;
			case 202:
				port->GiveHint(eHintPostScript, -1,
					form("gsave\n%d %d translate\n", p.x, p.y));
				a1= (short) is.GetBigEndian(2);
				a2= (short) is.GetBigEndian(2);
				a3= (short) is.GetBigEndian(2);
				a4= (short) is.GetBigEndian(2);
				port->GiveHint(eHintPostScript, -1,
					form("%d.%d %d.%d translate\n", a3, a4, a1, a2));
				l-= 8;
				break;

			default:
				// fprintf(stderr, "Comment(%d) %d\n", code, l);
				break;
			}
			is.seek(l, TRUE);
			break;
		case 0xfe:
			l= (int) is.GetBigEndian(4);
			is.seek(c, TRUE);
			break;
		case 0xff:      /* end */
			c= (short) is.GetBigEndian(2);
			done= 1;
			break;
				
		default:
			is.seek(op >> 7, TRUE);
			break;
		}
	}
}

static int PICTVersion(IStream &is)
{
	int pos= 0;
	u_char buf[512];

	for (int ntry= 0; ntry < 2; ntry++) {
		//is.seek(2+8, TRUE);
		is.read(buf, 2+8);
		if (is.fail())
			return 0;
		pos+= 10;
		u_short id= (short) is.GetBigEndian(2);
		if (is.fail())
			return 0;
		pos+= 2;
		if (id == 0x1101)
			return 1;
		if (id == 0x0011) {
			if ((short) is.GetBigEndian(2) == 0x02ff)
				return 2;
			if (is.fail()) {
				fprintf(stderr, "too short 2\n");
				return 0;
			}
			pos+= 2;
		}
		is.read(buf, 512-pos);
		//is.seek(512, FALSE);
		if (is.fail())
			return 0;
	}
	return 0;
}

//---- PICTFileTypeMatcher -----------------------------------------------------

class PICTFileTypeMatcher: public TypeMatcher {
public:
	PICTFileTypeMatcher(const Symbol &t) : TypeMatcher(t, 0, 0, FALSE)
		{ }
	bool MatchContents(Data*, const char*, int);
	Bitmap *GetBitmap()
		{ return gPictIcon; }
};

static PICTFileTypeMatcher pictmatcher(cDocTypeMacPict);

bool PICTFileTypeMatcher::MatchContents(Data*, const char *buf, int len)
{
	IStream is(buf, len);
	return PICTVersion(is) != 0;
}

//---- MacPictConverter --------------------------------------------------------

class MacPictConverter : public Converter {
public:
	MetaDef(MacPictConverter);
	MacPictConverter()
		{ }
	const char *AsString()
		{ return "Macintosh PICT"; }
	Object *Convert(Data *data, Class *want);
	bool CanConvert(Data *data, Class *want)
		{ return data->Type() == cDocTypeMacPict && want == Meta(Picture); }
};

NewMetaImpl0(MacPictConverter,Converter);

Object *MacPictConverter::Convert(Data *data, Class*)
{
	IStream is(data->GetStreamBufForReading(), 0, FALSE);
	int version= PICTVersion(is);
	if (version > 0) {
		Picture *pict= new Picture;
		PictPort *pp= new PictPort(pict);
		MacPictParse(is, pp, version);
		SafeDelete(pp);
		return pict;
	}
	return 0;
}

